// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "CaeEngineControl.h"
#include "../cae_dataPipe/CaeMsgHead.h"
#include "../cae_dataPipe/CaeDataPipe.h"
#include "../cae_CPHMediaEngine/CPHMediaEngine.h"
#include "../cae_agent/CaeAgent.h"
#include "../cae_common/CaeCommon.h"
#include "../cae_common/CaeDefine.h"
#include "../cae_dataPipe/CaeInput.h"
#include "CaeParamStorage.h"
#include "CaeMediaConfig.h"

using namespace std;
CaeEngineControl::CaeEngineControl()
{
    m_orientation = 255;
    m_changeOrientation = false;
    m_width = 0;
    m_height = 0;
    m_cphMediaEngine = nullptr;
}

CaeEngineControl::~CaeEngineControl()
{
    if (m_cphMediaEngine != nullptr) {
        delete m_cphMediaEngine;
        m_cphMediaEngine = nullptr;
    }
}

CaeEngineControl &CaeEngineControl::GetInstance()
{
    static CaeEngineControl instance;
    instance.Init();
    return instance;
}

int CaeEngineControl::Init()
{
    std::lock_guard<std::mutex> lock(m_initLock);
    if (m_cphMediaEngine == nullptr) {
        m_cphMediaEngine = new CPHMediaEngine();
        if (m_cphMediaEngine == nullptr) {
            LOGE("Init failed, failed to new CPHMediaEngine.");
            return -1;
        }
    }
    return 0;
}

void AudioEventCallback(uint8_t *data, uint32_t size, AudioCallbackExt &audioCallbackExt)
{
    CaePipeManager::AppendData(data, size, true, CAE_MSG_AUDIO);
}

void VideoEventCallback(uint8_t *data, uint32_t size, VideoCallbackExt &videoCallbackExt)
{
    if (CaeEngineControl::GetInstance().m_orientation != videoCallbackExt.orientation) {
        LOGI("Old orientation is %d, new orientation is %d", CaeEngineControl::GetInstance().m_orientation,
             videoCallbackExt.orientation);
        CaeEngineControl::GetInstance().m_orientation = videoCallbackExt.orientation;
        CaeEngineControl::GetInstance().m_changeOrientation = true;
    }

    // save latency data for stat
    CaeEngineControl::GetInstance().m_startCaptureTimestamp = videoCallbackExt.videoLatencyExt.startCaptureTimestamp;
    CaeEngineControl::GetInstance().m_endCaptureTimestamp = videoCallbackExt.videoLatencyExt.endCaptureTimestamp;
    CaeEngineControl::GetInstance().m_startEncodeTimestamp = videoCallbackExt.videoLatencyExt.startEncodeTimestamp;
    CaeEngineControl::GetInstance().m_endEncodeTimestamp = videoCallbackExt.videoLatencyExt.endEncodeTimestamp;
    
    CaePipeManager::AppendData(data, size, true, CAE_MSG_VIDEO);
}

uint32_t CaeEngineControl::GetCaptureLatency()
{
    uint32_t captureLatency = m_endCaptureTimestamp - m_startCaptureTimestamp;
    return captureLatency;
}

uint32_t CaeEngineControl::GetEncodeLatency()
{
    uint32_t encodeLatency = m_endEncodeTimestamp - m_startEncodeTimestamp;
    return encodeLatency;
}

Result CaeEngineControl::OpenMediaStream()
{
    m_orientation = 255;
    m_changeOrientation = false;
    if ((CaeMediaConfig::GetInstance().GetRemoteSchedulingElbIp().empty() ||
         CaeMediaConfig::GetInstance().GetRemoteSchedulingElbPort() == 0) &&
         CaeMediaConfig::GetInstance().GetVideoFrameType() == "h265") {
        LOGE("H265_NOT_SUPPORT");
        return H265_NOT_SUPPORT;
    }

    char* mediaConfigJson = CaeMediaConfig::GetInstance().BuildVideoJsonConfig();
    if (mediaConfigJson == nullptr) {
        return FAILED;
    }

    auto openRet =  static_cast<Result>(m_cphMediaEngine->OpenVideo(mediaConfigJson,
                                                              VideoEventCallback));
    if (openRet != SUCCESS) {
        return openRet;
    }

    if (OpenAudio() != SUCCESS) {
        m_cphMediaEngine->CloseVideo();
    }
    return SUCCESS;
}

Result CaeEngineControl::OpenAudio()
{
    if (CaeMediaConfig::GetInstance().IsMute()) {
        return SUCCESS;
    }
    char *audioJsonConfig = CaeMediaConfig::GetInstance().BuildAudioJsonConfig();
    if (audioJsonConfig == nullptr) {
        return FAILED;
    }
    auto openRet = static_cast<Result>(m_cphMediaEngine->OpenAudio(audioJsonConfig,
                                                              AudioEventCallback));
    if (openRet != SUCCESS) {
        LOGE("Open audio failed, startRet = %d", openRet);
        return openRet;
    }
    return SUCCESS;
}

Result CaeEngineControl::CloseMediaStream()
{
    CloseAudio();
    auto closeRet = static_cast<Result>(m_cphMediaEngine->CloseVideo());
    if (closeRet != Result::SUCCESS) {
        LOGE("Failed to stop video engines. %d", closeRet);
    }
    return closeRet;
}

Result CaeEngineControl::CloseAudio()
{
    auto closeRet = static_cast<Result>(m_cphMediaEngine->CloseAudio());
    if (closeRet != Result::SUCCESS) {
        LOGE("Failed to stop engines. %d", closeRet);
    }
    return SUCCESS;
}

bool CaeEngineControl::OpenVirtualDevices()
{
    if (CaeConfigManage::EnableVirtualDevice()) {
        for (int deviceType = CAMERA; deviceType <= SENSOR; ++deviceType) {
            if (GetVmiDeviceStatus((VmiDeviceType) deviceType) != VMI_INVALID) {
                LOGI("Virtual device %d is already enable.", deviceType);
                continue;
            }
            VmiConfigParams configParams;
            if (deviceType == CAMERA) {
                configParams.dataType = ENABLE_INJECT_LOCAL_CAMERA_STREAM ? (DataType)CaeVirtualDeviceConfig::GetLocalCameraStreamInjectFileType() : VMI_H264;
            }
            Result ret = InitVmiDevice((VmiDeviceType) deviceType, configParams);
            if (ret == Result::FAILED) {
                LOGE("Failed to init virtual device %d.", deviceType);
                return false;
            }
            ret = EnableVmiDevice((VmiDeviceType) deviceType);
            if (ret == Result::FAILED) {
                LOGE("Failed to enable virtual device %d.", deviceType);
                return false;
            }
        }
        LOGI("Enable virtual device successfully.");
    }

    return true;
}

void CaeEngineControl::CloseVirtualDevices()
{
    if (!CaeConfigManage::EnableVirtualDevice()) {
        return;
    }

    for (int type = CAMERA; type <= SENSOR; ++type) {
        if (CaeEngineControl::GetInstance().GetVmiDeviceStatus((VmiDeviceType)type) == VMI_INVALID) {
            LOGE("Device %d is already invalid.", type);
            continue;
        }
        Result ret = CaeEngineControl::GetInstance().DisableVmiDevice((VmiDeviceType)type);
        if(ret == Result::FAILED) {
            LOGE("Failed to disable virtual device %d, ret is %d.", type, ret);
            continue;
        }
        ret = CaeEngineControl::GetInstance().DestroyVmiDevice((VmiDeviceType)type);
        if(ret == Result::FAILED) {
            LOGE("Failed to destroy virtual device %d, ret is %d.", type, ret);
        }
    }
    LOGI("Destroy virtual device successfully.");
}

Result CaeEngineControl::SetMediaConfig()
{
    char* videoParamsJson = CaeMediaConfig::GetInstance().BuildDynamicVideoJsonConfig();
    return (Result) m_cphMediaEngine->SetParams(videoParamsJson);
}

Result CaeEngineControl::RequestIframe()
{
    return (Result) m_cphMediaEngine->GenerateKeyFrame();
}

Result CaeEngineControl::SetPhysicalResolution()
{
    CaeMediaConfig::GetInstance().SetNeedSetPhysicalResolution(false);
    return (Result) m_cphMediaEngine->SetPhysicalResolution(CaeMediaConfig::GetInstance().GetPhysicalWidth(),
                                                            CaeMediaConfig::GetInstance().GetPhysicalHeight());
}

void VirtualMicrophoneCallback(uint8_t *data, uint32_t size, VmiDeviceCallbackExt &callbackExt)
{
    CaePipeManager::GetInstance().AppendData(data, size, true, CAE_MSG_VIRTUAL_MICROPHONE);
}

void VirtualCameraCallback(uint8_t *data, uint32_t size, VmiDeviceCallbackExt &callbackExt)
{
    CaePipeManager::GetInstance().AppendData(data, size, true, CAE_MSG_VIRTUAL_CAMERA);
}

void VirtualSensorCallback(uint8_t *data, uint32_t size, VmiDeviceCallbackExt &callbackExt)
{
    CaePipeManager::GetInstance().AppendData(data, size, true, CAE_MSG_VIRTUAL_SENSOR);
}

Result CaeEngineControl::InitVmiDevice(VmiDeviceType deviceType, VmiConfigParams &params) {
    VmiDeviceCallback virtualDeviceCallback;
    switch (deviceType) {
        case CAMERA:
            virtualDeviceCallback = VirtualCameraCallback;
            break;
        case MICROPHONE:
            virtualDeviceCallback = VirtualMicrophoneCallback;
            break;
        case SENSOR:
            virtualDeviceCallback = VirtualSensorCallback;
            break;
        default: {
            LOGE("virtual device type %d is not support.", deviceType);
            return Result::FAILED;
        }
    }
    return m_cphMediaEngine->InitVmiDevice(deviceType, params, virtualDeviceCallback) == 0 ? Result::SUCCESS : Result::FAILED;
}

Result CaeEngineControl::EnableVmiDevice(VmiDeviceType deviceType) {
    return static_cast<Result>(m_cphMediaEngine->EnableVmiDevice(deviceType));
}

Result CaeEngineControl::DisableVmiDevice(VmiDeviceType deviceType) {
    return static_cast<Result>(m_cphMediaEngine->DisableVmiDevice(deviceType));
}

Result CaeEngineControl::DestroyVmiDevice(VmiDeviceType deviceType) {
    return static_cast<Result>(m_cphMediaEngine->DestroyVmiDevice(deviceType));
}

Result CaeEngineControl::VmiDeviceSend(VmiDeviceType deviceType, uint8_t* data, uint32_t len) {
    return static_cast<Result>(m_cphMediaEngine->VmiDeviceSend(deviceType, data, len));
}

uint8_t CaeEngineControl::GetVmiDeviceStatus(VmiDeviceType deviceType) {
    return m_cphMediaEngine->GetVmiDeviceStatus(deviceType);
}

int CaeEngineControl::HandleTouchMsg(CaeTouchEventMsg *caeTouchEventMsg)
{
    InputTouch touchEventMsg;
    touchEventMsg.pointerID = caeTouchEventMsg->GetId();
    touchEventMsg.orientation = caeTouchEventMsg->GetOrientation();
    touchEventMsg.action = caeTouchEventMsg->GetAction();
    touchEventMsg.pressure = caeTouchEventMsg->GetPressure();
    touchEventMsg.touchX = caeTouchEventMsg->GetX();
    touchEventMsg.touchY = caeTouchEventMsg->GetY();
    touchEventMsg.physicalWidth = caeTouchEventMsg->GetWidth();
    touchEventMsg.physicalHeight = caeTouchEventMsg->GetHeight();
    touchEventMsg.touchTime = caeTouchEventMsg->GetTime();

    LOGD("touchEventMsg.pointerID: %d.", touchEventMsg.pointerID);
    LOGD("touchEventMsg.orientation: %d.", touchEventMsg.orientation);
    LOGD("touchEventMsg.action: %d.", touchEventMsg.action);
    LOGD("touchEventMsg.pressure: %d.", touchEventMsg.pressure);
    LOGD("touchEventMsg.touchX: %d.", touchEventMsg.touchX);
    LOGD("touchEventMsg.touchY: %d.", touchEventMsg.touchY);
    LOGD("touchEventMsg.physicalWidth: %d.", touchEventMsg.physicalWidth);
    LOGD("touchEventMsg.physicalHeight: %d.", touchEventMsg.physicalHeight);
    LOGD("touchEventMsg.touchTime: %d.", touchEventMsg.touchTime);
    LOGD("sizeof(touchEventMsg) %d", sizeof(touchEventMsg));
    return m_cphMediaEngine->InjectTouchData(touchEventMsg);
}

int CaeEngineControl::HandleKeyMsg(CaeKeyEventMsg *caeKeyEventMsg)
{
    InputKey keyEventMsg;
    keyEventMsg.action = caeKeyEventMsg->GetAction();
    keyEventMsg.keyCode = caeKeyEventMsg->GetKeycode();
    return m_cphMediaEngine->InjectKeyData(keyEventMsg);
}

const char* CaeEngineControl::GetMediaFeatures()
{
    return m_cphMediaEngine->GetCPHMediaFeatures();
}